package com.lx.mall.gateway.filter;

import com.alibaba.cloud.commons.lang.StringUtils;
import com.alibaba.fastjson.JSON;
import com.lx.mall.common.api.ResultCode;
import com.lx.mall.common.exception.GateWayException;
import com.lx.mall.gateway.properties.NotAuthUrlProperties;
import com.lx.mall.gateway.utils.JwtUtils;
import io.jsonwebtoken.Claims;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.annotation.Order;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.security.PublicKey;
import java.util.Map;

/**
 * @author lX
 * @version JDK 8
 * @className AuthenticationFilter (此处以class为例)
 * @date 2024/12/17
 * @description 认证过滤器
 */
@Component
@Order(0)
@EnableConfigurationProperties(value = NotAuthUrlProperties.class)
public class AuthenticationFilter implements GlobalFilter, InitializingBean {

  Logger logger = LoggerFactory.getLogger(AuthenticationFilter.class);


  /**
   * 不需要认证的URL
   */
  @Autowired
  private NotAuthUrlProperties notAuthUrlProperties;

  /**
   * jwt的公钥,需要网关启动,远程调用认证中心去获取公钥
   */
  private PublicKey publicKey;

  @Autowired
  private RestTemplate restTemplate;


  @Override
  public void afterPropertiesSet() throws Exception {

    this.publicKey = JwtUtils.genPublicKey(restTemplate);

  }

  @Override
  public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

    //1.获取请求的URI
    String currentUrl = exchange.getRequest().getURI().getPath();

    //1. 过滤不需要认证的url
    if(shouldSkip(currentUrl)) {
      logger.info("跳过认证的URL:{}",currentUrl);
      return chain.filter(exchange);
    }
    logger.info("需要认证的URL:{}",currentUrl);


    //2. 获取token
    // 从请求头中解析 Authorization  value:  bearer xxxxxxx
    // 或者从请求参数中解析 access_token
    //第一步:解析出我们Authorization的请求头  value为: “bearer XXXXXXXXXXXXXX”
    String authHeader = exchange.getRequest().getHeaders().getFirst("Authorization");

    //第二步:判断Authorization的请求头是否为空
    if(StringUtils.isEmpty(authHeader)) {
      logger.warn("需要认证的url,请求头为空");
      throw new GateWayException(ResultCode.AUTHORIZATION_HEADER_IS_EMPTY);
    }

    //第三步. 校验token
    // 拿到token后，通过公钥（需要从授权服务获取公钥）校验
    // 校验失败或超时抛出异常
    //第三步 校验我们的jwt 若jwt不对或者超时都会抛出异常
    Claims claims = JwtUtils.validateJwtToken(authHeader,publicKey);

    //4. 校验通过后，从token中获取的用户登录信息存储到请求头中
    //第四步 把从jwt中解析出来的 用户登陆信息存储到请求头中
    ServerWebExchange webExchange = wrapHeader(exchange,claims);

    return chain.filter(exchange);
  }

  private boolean shouldSkip(String currentUrl) {
    //路径匹配器(简介SpringMvc拦截器的匹配器)
    //比如/oauth/** 可以匹配/oauth/token    /oauth/check_token等
    PathMatcher pathMatcher = new AntPathMatcher();
    for(String skipPath:notAuthUrlProperties.getShouldSkipUrls()) {
      if(pathMatcher.match(skipPath,currentUrl)) {
        return true;
      }
    }
    return false;
  }

  private ServerWebExchange wrapHeader(ServerWebExchange serverWebExchange,Claims claims) {

    logger.info("jwt的用户信息:{}",JSON.toJSONString(claims));

    String memberId = claims.get("additionalInfo", Map.class).get("memberId").toString();

    String nickName = claims.get("additionalInfo",Map.class).get("nickName").toString();

    //向headers中放文件，记得build
    ServerHttpRequest request = serverWebExchange.getRequest().mutate()
      .header("username",claims.get("user_name",String.class))
      .header("memberId",memberId)
      .header("nickName",nickName)
      .build();

    //将现在的request 变成 change对象
    return serverWebExchange.mutate().request(request).build();
  }
}
